﻿//================================================================================================
// Credits:	        Arunkumar Viswanathan
// Source:          http://www.codeproject.com/KB/IP/IpXconfig.aspx
//================================================================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using ipxconfig;
using System.Net.Sockets;
using System.Net.NetworkInformation;
using System.Net;

namespace TrafficSplitter
{
    class Routing
    {
        [DllImport("Iphlpapi.dll")]
        [return: MarshalAs(UnmanagedType.U4)]
        static extern int CreateIpForwardEntry(ref MIB_IPFORWARDROW pRoute);

        [DllImport("Iphlpapi.dll")]
        [return: MarshalAs(UnmanagedType.U4)]
        static extern int DeleteIpForwardEntry(ref MIB_IPFORWARDROW pRoute);

        [DllImport("iphlpapi.dll", SetLastError = true)]
        static extern int GetAdapterIndex(string adapter, out int index);

        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_IPFORWARDROW
        {
            public UInt32 dwForwardDest;
            public UInt32 dwForwardMask;
            public UInt32 dwForwardPolicy;
            public UInt32 dwForwardNextHop;
            public UInt32 dwForwardIfIndex;
            public UInt32 dwForwardType;
            public UInt32 dwForwardProto;
            public UInt32 dwForwardAge;
            public UInt32 dwForwardNextHopAS;
            public UInt32 dwForwardMetric1;
            public UInt32 dwForwardMetric2;
            public UInt32 dwForwardMetric3;
            public UInt32 dwForwardMetric4;
            public UInt32 dwForwardMetric5;
        }

        const int ERROR_ACCESS_DENIED = 5;
        const int ERROR_INVALID_PARAMETER = 87;
        const int ERROR_NOT_SUPPORTED = 50;
        const int ERROR_OBJECT_EXISTS = 5010; // The object already exists
        const string ERROR_OBJECT_EXISTS_MSG = "The route already exists"; //"The object already exists";
        const int ERROR_ELEMENT_NOT_FOUND = 1168; // Element not found
        const string ERROR_ELEMENT_NOT_FOUND_MSG = "Cannot delete a route that doesn't exist"; //"Element not found";
        const int PROTO_IP_NETMGMT = 3;

        [StructLayout(LayoutKind.Sequential)]
        public struct MIB_IPINTERFACE_ROW
        {
            public uint Family;
            public ulong InterfaceLuid;
            public uint InterfaceIndex;
            public uint MaxReassemblySize;
            public ulong InterfaceIdentifier;
            public uint MinRouterAdvertisementInterval;
            public uint MaxRouterAdvertisementInterval;
            public byte AdvertisingEnabled;
            public byte ForwardingEnabled;
            public byte WeakHostSend;
            public byte WeakHostReceive;
            public byte UseAutomaticMetric;
            public byte UseNeighborUnreachabilityDetection;
            public byte ManagedAddressConfigurationSupported;
            public byte OtherStatefulConfigurationSupported;
            public byte AdvertiseDefaultRoute;
            public uint RouterDiscoveryBehavior;
            public uint DadTransmits;
            public uint BaseReachableTime;
            public uint RetransmitTime;
            public uint PathMtuDiscoveryTimeout;
            public uint LinkLocalAddressBehavior;
            public uint LinkLocalAddressTimeout;
            public uint ZoneIndice0, ZoneIndice1, ZoneIndice2, ZoneIndice3, ZoneIndice4, ZoneIndice5, ZoneIndice6, ZoneIndice7,
             ZoneIndice8, ZoneIndice9, ZoneIndice10, ZoneIndice11, ZoneIndice12, ZoneIndice13, ZoneIndice14, ZoneIndice15;
            public uint SitePrefixLength;
            public uint Metric;
            public uint NlMtu;
            public byte Connected;
            public byte SupportsWakeUpPatterns;
            public byte SupportsNeighborDiscovery;
            public byte SupportsRouterDiscovery;
            public uint ReachableTime;
            public byte TransmitOffload;
            public byte ReceiveOffload;
            public byte DisableDefaultRoutes;
        }

        [DllImport("Iphlpapi.dll")]
        static extern uint GetIpInterfaceEntry(ref MIB_IPINTERFACE_ROW pRoute);

        const int MAX_ADAPTER_DESCRIPTION_LENGTH = 128;
        const int ERROR_BUFFER_OVERFLOW = 111;
        const int MAX_ADAPTER_NAME_LENGTH = 256;
        const int MAX_ADAPTER_ADDRESS_LENGTH = 8;
        const int MIB_IF_TYPE_OTHER = 1;
        const int MIB_IF_TYPE_ETHERNET = 6;
        const int MIB_IF_TYPE_TOKENRING = 9;
        const int MIB_IF_TYPE_FDDI = 15;
        const int MIB_IF_TYPE_PPP = 23;
        const int MIB_IF_TYPE_LOOPBACK = 24;
        const int MIB_IF_TYPE_SLIP = 28;

        public static uint GetMetric(uint index)
        {
            System.OperatingSystem osInfo = System.Environment.OSVersion;
            if (osInfo.Version.Major >= 6)
            {
                MIB_IPINTERFACE_ROW aRow = new MIB_IPINTERFACE_ROW();
                aRow.Family = 2;
                aRow.InterfaceLuid = 0;
                aRow.InterfaceIndex = index;

                //GetIpInterfaceEntry is available in Vista/2008 server or higher 
                uint errorCode = GetIpInterfaceEntry(ref aRow);

                return aRow.Metric;
            }
            if (((osInfo.Version.Major == 5) && (osInfo.Version.Minor < 1)) || (osInfo.Version.Major < 5))
                //Console.Write("Unsupported version of Windows");
                return 1;
            else
                return 1;
        }

        public static Boolean Modify(Boolean add, String destination, String mask, String gateway, UInt32 index, UInt32 metric)
        {
            MIB_IPFORWARDROW route = new MIB_IPFORWARDROW();

            if (index == 0)
            {
                Constants.AppendToLogBuffer(LOG_TYPE.Error, "Unable to manipulate routing table: Network Interface Index not found");
                return false;
            }
            else if (metric < 1)
            {
                Constants.AppendToLogBuffer(LOG_TYPE.Error, "Unable to manipulate routing table: Metric value not found");
                return false;
            }

            route.dwForwardDest = GetIPFromString(destination);
            route.dwForwardMask = GetIPFromString(mask);
            route.dwForwardNextHop = GetIPFromString(gateway);
            route.dwForwardIfIndex = index;
            route.dwForwardMetric1 = metric;
            route.dwForwardProto = PROTO_IP_NETMGMT;

            int ret;
            if (add)
                ret = CreateIpForwardEntry(ref route);
            else
                ret = DeleteIpForwardEntry(ref route);
            if (ret == 0)
            {
                return true;
            }
            else if (ret == ERROR_INVALID_PARAMETER)
            {
                route.dwForwardNextHop = 0;
                if (add)
                    ret = CreateIpForwardEntry(ref route);
                else
                    ret = DeleteIpForwardEntry(ref route);
            }

            if (ret == 0)
            {
                return true;
            }
            else if (ret == ERROR_OBJECT_EXISTS)
            {
                Constants.AppendToLogBuffer(LOG_TYPE.Debug, "Unable to manipulate routing table: " + ERROR_OBJECT_EXISTS_MSG);
                return true;
            }
            else if (ret == ERROR_ELEMENT_NOT_FOUND)
            {
                Constants.AppendToLogBuffer(LOG_TYPE.Debug, "Unable to manipulate routing table: " + ERROR_ELEMENT_NOT_FOUND_MSG);
                return true;
            }
            else
            {
                string errorMessage = new System.ComponentModel.Win32Exception(ret).Message;
                Constants.AppendToLogBuffer(LOG_TYPE.Error, "Unable to manipulate routing table: " + errorMessage);
                return false;
            }
        }

        /*
         * Gets the inverse numerical value of the IP address - use with IPForwarding
         */
        public static UInt32 GetIPFromString(String address)
        {
            if ((address == null) || (address == ""))
                return 0;

            string[] parts = address.Split('.');
            return ((UInt32.Parse(parts[3]) << 24) +
              ((UInt32.Parse(parts[2])) << 16) +
              ((UInt32.Parse(parts[1])) << 8) +
              UInt32.Parse(parts[0]));
        }

        public static ulong IP2Int(string address)
        {
            if ((address == null) || (address == ""))
                return 0;

            string[] parts = address.Split('.');
            return ((UInt32.Parse(parts[0]) << 24) +
              ((UInt32.Parse(parts[1])) << 16) +
              ((UInt32.Parse(parts[2])) << 8) +
              UInt32.Parse(parts[3]));
        }

        public static void GetStats(VirtualAdapter adapter, out long bytesRecv, out long bytesSent)
        {
            try
            {
                IPv4InterfaceStatistics stats = adapter.GetNetworkInterface().GetIPv4Statistics();

                bytesRecv = stats.BytesReceived;
                bytesSent = stats.BytesSent;
            }
            catch (Exception)
            {
                bytesRecv = 0;
                bytesSent = 0;
            }
        }

        // Get Gateway & Metric
        public static void GetAdapterGatewayMetric(VirtualAdapter adapter)
        {
            if (adapter.GetNetworkInterface() != null)
            {
                SetAdapterProperties(adapter);
            }
            else
            {
                NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();

                foreach (NetworkInterface adapt in nics)
                {
                    // Only display informatin for interfaces that support IPv4.
                    if (adapt.Supports(NetworkInterfaceComponent.IPv4) == false)
                    {
                        continue;
                    }

                    IPInterfaceProperties adapterProperties = adapt.GetIPProperties();
                    
                    // Try to get the IPv4 interface properties.
                    IPv4InterfaceProperties p = adapterProperties.GetIPv4Properties();

                    if (p == null)
                    {
                        continue;
                    }

                    if (p.Index == (uint)adapter.GetIndex())
                    {
                        adapter.SetNetworkInterface(adapt);
                        SetAdapterProperties(adapter);
                        return;
                    }
                }
            }
        }

        public static void SetAdapterProperties(VirtualAdapter adapter)
        {
            // Try to get the IPv4 interface properties.
            IPInterfaceProperties props = adapter.GetInterfaceProperties();
            IPv4InterfaceProperties p = props.GetIPv4Properties();

            adapter.SetGateway(GetGatewayAddress(props));
            adapter.SetIPAddress(GetIPAddress(props));
            adapter.SetMetric(Routing.GetMetric((uint)p.Index));
        }

        public static String GetGatewayAddress(IPInterfaceProperties adapterProperties)
        {
            GatewayIPAddressInformationCollection gateways = adapterProperties.GatewayAddresses;
            if (gateways.Count > 0)
            {
                foreach (GatewayIPAddressInformation gateway in gateways)
                {
                    return gateway.Address.ToString();
                }
            }

            return null;
        }

        public static String GetIPAddress(IPInterfaceProperties adapterProperties)
        {
            UnicastIPAddressInformationCollection ipaddresses = adapterProperties.UnicastAddresses;
            if (ipaddresses.Count > 0)
            {
                foreach (UnicastIPAddressInformation ipaddress in ipaddresses)
                {
                    return ipaddress.Address.ToString();
                }
            }

            return null;
        }

        public static void GetAdapterInfo(VirtualAdapter adapter)
        {
            if (adapter.GetIndex() == 0)
            {
                adapter.SetMetric(0);
                adapter.SetGateway(null);
            }
            else
            {
                GetAdapterGatewayMetric(adapter);
            }
        }

        // Get Adapter index
        public static void FindIndex(VirtualAdapter adapter)
        {
            // Check for supported operating system
            if (IpHlpNetworkAdapterUtil.TryGetAdaptersAddresses())
            {
                List<IP_ADAPTER_ADDRESSES_XP2K3> adaptersAddressesCollection;
                IpHlpNetworkAdapterUtil adapterUtils = new IpHlpNetworkAdapterUtil();

                try
                {
                    adapterUtils.GetAdaptersAddresses(AddressFamily.Unspecified, GAA_FLAGS.GAA_FLAG_DEFAULT, out adaptersAddressesCollection);

                    foreach (IP_ADAPTER_ADDRESSES_XP2K3 adapterAddressesBuffer in adaptersAddressesCollection)
                    {
                        string FriendlyName = Marshal.PtrToStringAuto(adapterAddressesBuffer.FriendlyName);

                        if (adapter.Equals(FriendlyName))
                        {
                            adapter.SetIndex(adapterAddressesBuffer.IfIndex);
                            return;
                        }
                    }
                }
                catch (System.ComponentModel.Win32Exception)
                {
                }
            }

            adapter.SetIndex(0);
        }

        // Enumerate ONLY (ETHERNET_CSMACD) devices and not RAS interaces
        public static void Enumerate(List<VirtualAdapter> adapters)
        {
            Constants.AppendToLogBuffer(LOG_TYPE.Debug, "Enumerating network interfaces...");

            // Check for supported operating system
            if (IpHlpNetworkAdapterUtil.TryGetAdaptersAddresses())
            {
                List<IP_ADAPTER_ADDRESSES_XP2K3> adaptersAddressesCollection;
                IpHlpNetworkAdapterUtil adapterUtils = new IpHlpNetworkAdapterUtil();

                adapterUtils.GetAdaptersAddresses(AddressFamily.Unspecified, GAA_FLAGS.GAA_FLAG_DEFAULT, out adaptersAddressesCollection);

                foreach (IP_ADAPTER_ADDRESSES_XP2K3 adapterAddressesBuffer in adaptersAddressesCollection)
                {
                    string adapterName = Marshal.PtrToStringAnsi(adapterAddressesBuffer.AdapterName);
                    string FriendlyName = Marshal.PtrToStringAuto(adapterAddressesBuffer.FriendlyName);
                    string description = Marshal.PtrToStringAuto(adapterAddressesBuffer.Description);

                    Constants.AppendToLogBuffer(LOG_TYPE.Debug, "> " + FriendlyName.ToString());
                    IF_TYPE iftype = (IF_TYPE)adapterAddressesBuffer.IfType;

                    if ((iftype == IF_TYPE.IF_TYPE_ETHERNET_CSMACD) || (iftype == IF_TYPE.IF_TYPE_IEEE80211))
                    {
                        Constants.AppendToLogBuffer(LOG_TYPE.Debug, "... adding Interface to list.");
                        adapters.Add(new VirtualAdapter(FriendlyName.ToString(), (UInt32)adapterAddressesBuffer.IfIndex, TS_ADAPTER_TYPES.NetworkAdapter, 0));
                    }
                }
            }

            Constants.FlushLogBuffer();
        }
    }
}
